use std::{
    any::{Any, TypeId},
    hash::{Hash, Hasher},
};

/// A dynamic-dispatch version of the [`PartialEq`] trait. Implemented for every type that
/// implements [`Any`] + [`PartialEq`].
pub trait DynPartialEq: Any {
    fn dyn_partial_eq(&self, other: &dyn Any) -> bool;
}

impl<T: Any + PartialEq> DynPartialEq for T {
    fn dyn_partial_eq(&self, other: &dyn Any) -> bool {
        other
            .downcast_ref::<Self>()
            .map(|other| PartialEq::eq(self, other))
            .unwrap_or(false)
    }
}

#[macro_export]
macro_rules! impl_partial_eq_for_dyn {
    ($ty:ty) => {
        impl ::std::cmp::PartialEq for $ty {
            fn eq(&self, other: &Self) -> bool {
                $crate::DynPartialEq::dyn_partial_eq(self, other as &dyn ::std::any::Any)
            }
        }
    };
}

/// A dynamic-dispatch version of the [`Eq`] trait. Implemented for every type that implements
/// [`Any`] + [`Eq`].
pub trait DynEq: DynPartialEq {}

impl<T: Any + Eq> DynEq for T {}

#[macro_export]
macro_rules! impl_eq_for_dyn {
    ($ty:ty) => {
        impl ::std::cmp::Eq for $ty {}
    };
}

/// A dynamic-dispatch version of the [`Hash`] trait. Implemented for every type that implements
/// [`Any`] + [`Hash`].
///
/// The `TypeId::of::<Self>()` value is included in the resulting hash, so the hash generated by
/// `DynEqHash` will not match the hash generated by the underlying `Hash` implementation. In other
/// words, the hash of `T` will not match the hash of `T as dyn SomeDynHashTrait`.
pub trait DynHash {
    fn dyn_hash(&self, state: &mut dyn Hasher);
}

impl<T: Any + Hash> DynHash for T {
    fn dyn_hash(&self, mut state: &mut dyn Hasher) {
        Hash::hash(&(TypeId::of::<Self>(), self), &mut state);
    }
}

#[macro_export]
macro_rules! impl_hash_for_dyn {
    ($ty:ty) => {
        impl ::std::hash::Hash for $ty {
            fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) {
                $crate::DynHash::dyn_hash(self, state as &mut dyn (::std::hash::Hasher))
            }
        }
    };
}
